package com.example.backend.service;

import com.example.backend.annotation.LogExecution;
import com.example.backend.domain.entity.File;
import com.example.backend.domain.enums.FileStatus;
import com.example.backend.domain.enums.FileType;
import com.example.backend.dto.*;
import com.example.backend.exception.BusinessException;
import com.example.backend.repository.FileRepository;
import com.example.backend.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * 文件服务
 */
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class FileService {
    
    private final FileRepository fileRepository;
    private final UserRepository userRepository;
    private final FileStorageService fileStorageService;
    
    private static final String UPLOAD_DIR = "uploads";
    private static final long MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
    
    @LogExecution("获取文件列表")
    @Cacheable(value = "files", key = "#filter + '_' + #pageable")
    @Transactional(readOnly = true)
    public Page<FileDto> findAll(FileFilter filter, Pageable pageable) {
        Specification<File> spec = buildSpecification(filter);
        Page<File> files = fileRepository.findAll(spec, pageable);
        return files.map(this::convertToDto);
    }
    
    @LogExecution("获取文件详情")
    @Cacheable(value = "file", key = "#id")
    @Transactional(readOnly = true)
    public Optional<FileDto> findById(Long id) {
        return fileRepository.findById(id).map(this::convertToDto);
    }
    
    @LogExecution("上传文件")
    @CacheEvict(value = {"files", "file_stats"}, allEntries = true)
    public FileDto uploadFile(MultipartFile file, Long uploadedBy, Long ticketId, Long commentId, String description) {
        try {
            // 验证文件
            validateFile(file);
            
            // 生成文件名和路径
            String originalFileName = file.getOriginalFilename();
            String fileName = generateFileName(originalFileName);
            String filePath = generateFilePath(fileName);
            
            // 保存文件到磁盘
            fileStorageService.saveFile(file, filePath);
            
            // 计算文件哈希
            String fileHash = calculateFileHash(file.getBytes());
            
            // 检查文件是否已存在
            Optional<File> existingFile = fileRepository.findByFileHash(fileHash);
            if (existingFile.isPresent()) {
                log.warn("文件已存在，文件哈希: {}", fileHash);
                // 可以选择返回已存在的文件或抛出异常
                throw new BusinessException("文件已存在");
            }
            
            // 创建文件记录
            File fileEntity = File.builder()
                    .fileName(fileName)
                    .originalFileName(originalFileName)
                    .filePath(filePath)
                    .fileSize(file.getSize())
                    .mimeType(file.getContentType())
                    .fileType(detectFileType(file.getContentType(), originalFileName))
                    .status(FileStatus.ACTIVE)
                    .description(description)
                    .uploadedBy(uploadedBy)
                    .ticketId(ticketId)
                    .commentId(commentId)
                    .fileHash(fileHash)
                    .downloadCount(0)
                    .build();
            
            File savedFile = fileRepository.save(fileEntity);
            log.info("文件上传成功: {}", savedFile.getFileName());
            
            return convertToDto(savedFile);
            
        } catch (IOException e) {
            log.error("文件上传失败: {}", e.getMessage(), e);
            throw new BusinessException("文件上传失败: " + e.getMessage());
        }
    }
    
    @LogExecution("下载文件")
    @CacheEvict(value = "file", key = "#id")
    public FileDownloadResult downloadFile(Long id) {
        File file = fileRepository.findById(id)
                .orElseThrow(() -> new BusinessException("文件不存在"));
        
        try {
            byte[] content = fileStorageService.loadFile(file.getFilePath());
            
            // 更新下载次数和最后访问时间
            file.setDownloadCount(file.getDownloadCount() + 1);
            file.setLastAccessedAt(Instant.now());
            fileRepository.save(file);
            
            return FileDownloadResult.builder()
                    .fileName(file.getOriginalFileName())
                    .content(content)
                    .mimeType(file.getMimeType())
                    .fileSize(file.getFileSize())
                    .success(true)
                    .build();
                    
        } catch (IOException e) {
            log.error("文件下载失败: {}", e.getMessage(), e);
            return FileDownloadResult.builder()
                    .success(false)
                    .errorMessage("文件下载失败: " + e.getMessage())
                    .build();
        }
    }
    
    @LogExecution("批量下载文件")
    public FileDownloadResult.BatchDownloadResult batchDownloadFiles(List<Long> fileIds, String zipFileName) {
        List<File> files = fileRepository.findAllById(fileIds);
        
        if (files.isEmpty()) {
            throw new BusinessException("没有找到要下载的文件");
        }
        
        try {
            List<FileDownloadResult> downloadResults = files.stream()
                    .map(file -> {
                        try {
                            byte[] content = fileStorageService.loadFile(file.getFilePath());
                            
                            // 更新下载次数
                            file.setDownloadCount(file.getDownloadCount() + 1);
                            file.setLastAccessedAt(Instant.now());
                            fileRepository.save(file);
                            
                            return FileDownloadResult.builder()
                                    .fileName(file.getOriginalFileName())
                                    .content(content)
                                    .mimeType(file.getMimeType())
                                    .fileSize(file.getFileSize())
                                    .success(true)
                                    .build();
                        } catch (IOException e) {
                            return FileDownloadResult.builder()
                                    .fileName(file.getOriginalFileName())
                                    .success(false)
                                    .errorMessage("文件下载失败: " + e.getMessage())
                                    .build();
                        }
                    })
                    .collect(Collectors.toList());
            
            // 创建ZIP文件
            byte[] zipContent = fileStorageService.createZipFile(downloadResults, zipFileName);
            
            int successCount = (int) downloadResults.stream().filter(FileDownloadResult::isSuccess).count();
            int failureCount = downloadResults.size() - successCount;
            
            return FileDownloadResult.BatchDownloadResult.builder()
                    .zipFileName(zipFileName)
                    .zipContent(zipContent)
                    .zipSize((long) zipContent.length)
                    .files(downloadResults)
                    .successCount(successCount)
                    .failureCount(failureCount)
                    .totalCount(downloadResults.size())
                    .build();
                    
        } catch (IOException e) {
            log.error("批量下载文件失败: {}", e.getMessage(), e);
            throw new BusinessException("批量下载文件失败: " + e.getMessage());
        }
    }
    
    @LogExecution("删除文件")
    @CacheEvict(value = {"files", "file", "file_stats"}, allEntries = true)
    public void deleteFile(Long id) {
        File file = fileRepository.findById(id)
                .orElseThrow(() -> new BusinessException("文件不存在"));
        
        try {
            // 删除物理文件
            fileStorageService.deleteFile(file.getFilePath());
            
            // 更新文件状态为已删除
            file.setStatus(FileStatus.DELETED);
            fileRepository.save(file);
            
            log.info("文件删除成功: {}", file.getFileName());
            
        } catch (IOException e) {
            log.error("文件删除失败: {}", e.getMessage(), e);
            throw new BusinessException("文件删除失败: " + e.getMessage());
        }
    }
    
    @LogExecution("批量删除文件")
    @CacheEvict(value = {"files", "file", "file_stats"}, allEntries = true)
    public BatchResult batchDeleteFiles(List<Long> fileIds) {
        BatchResult result = BatchResult.builder()
                .totalCount(fileIds.size())
                .successCount(0)
                .failureCount(0)
                .build();
        
        for (Long fileId : fileIds) {
            try {
                deleteFile(fileId);
                result.setSuccessCount(result.getSuccessCount() + 1);
            } catch (Exception e) {
                result.addError("文件 " + fileId + " 删除失败: " + e.getMessage());
                result.setFailureCount(result.getFailureCount() + 1);
            }
        }
        
        return result;
    }
    
    @LogExecution("更新文件信息")
    @CacheEvict(value = {"files", "file"}, allEntries = true)
    public FileDto updateFile(Long id, String description, String tags) {
        File file = fileRepository.findById(id)
                .orElseThrow(() -> new BusinessException("文件不存在"));
        
        file.setDescription(description);
        file.setTags(tags);
        
        File updatedFile = fileRepository.save(file);
        return convertToDto(updatedFile);
    }
    
    @LogExecution("获取文件统计")
    @Cacheable(value = "file_stats")
    @Transactional(readOnly = true)
    public FileStatsDto getFileStatistics() {
        long totalFiles = fileRepository.count();
        long totalSize = fileRepository.getTotalFileSize() != null ? fileRepository.getTotalFileSize() : 0;
        long totalDownloads = fileRepository.getTotalDownloads() != null ? fileRepository.getTotalDownloads() : 0;
        
        long imageCount = fileRepository.countByFileType(FileType.IMAGE);
        long documentCount = fileRepository.countByFileType(FileType.DOCUMENT);
        long videoCount = fileRepository.countByFileType(FileType.VIDEO);
        long audioCount = fileRepository.countByFileType(FileType.AUDIO);
        long archiveCount = fileRepository.countByFileType(FileType.ARCHIVE);
        long codeCount = fileRepository.countByFileType(FileType.CODE);
        long otherCount = fileRepository.countByFileType(FileType.OTHER);
        
        long activeFiles = fileRepository.countByStatus(FileStatus.ACTIVE);
        long archivedFiles = fileRepository.countByStatus(FileStatus.ARCHIVED);
        long deletedFiles = fileRepository.countByStatus(FileStatus.DELETED);
        long quarantineFiles = fileRepository.countByStatus(FileStatus.QUARANTINE);
        
        Instant today = Instant.now().minusSeconds(24 * 3600);
        Instant week = Instant.now().minusSeconds(7 * 24 * 3600);
        Instant month = Instant.now().minusSeconds(30 * 24 * 3600);
        
        long todayUploads = fileRepository.countByCreatedAtAfter(today);
        long weekUploads = fileRepository.countByCreatedAtAfter(week);
        long monthUploads = fileRepository.countByCreatedAtAfter(month);
        
        return FileStatsDto.builder()
                .totalFiles(totalFiles)
                .totalSize(totalSize)
                .imageCount(imageCount)
                .documentCount(documentCount)
                .videoCount(videoCount)
                .audioCount(audioCount)
                .archiveCount(archiveCount)
                .codeCount(codeCount)
                .otherCount(otherCount)
                .activeFiles(activeFiles)
                .archivedFiles(archivedFiles)
                .deletedFiles(deletedFiles)
                .quarantineFiles(quarantineFiles)
                .totalDownloads(totalDownloads)
                .todayUploads(todayUploads)
                .weekUploads(weekUploads)
                .monthUploads(monthUploads)
                .build();
    }
    
    @LogExecution("搜索文件")
    @Transactional(readOnly = true)
    public Page<FileDto> searchFiles(String keyword, FileType fileType, FileStatus status, Long uploadedBy, Long ticketId, Pageable pageable) {
        Page<File> files = fileRepository.searchFiles(keyword, fileType, status, uploadedBy, ticketId, pageable);
        return files.map(this::convertToDto);
    }
    
    @LogExecution("获取用户文件")
    @Transactional(readOnly = true)
    public Page<FileDto> getUserFiles(Long userId, Pageable pageable) {
        Page<File> files = fileRepository.findByUploadedByAndStatus(userId, FileStatus.ACTIVE, pageable);
        return files.map(this::convertToDto);
    }
    
    @LogExecution("获取工单文件")
    @Transactional(readOnly = true)
    public Page<FileDto> getTicketFiles(Long ticketId, Pageable pageable) {
        Page<File> files = fileRepository.findByTicketIdAndStatus(ticketId, FileStatus.ACTIVE, pageable);
        return files.map(this::convertToDto);
    }
    
    // 私有方法
    
    private void validateFile(MultipartFile file) {
        if (file.isEmpty()) {
            throw new BusinessException("文件不能为空");
        }
        
        if (file.getSize() > MAX_FILE_SIZE) {
            throw new BusinessException("文件大小不能超过100MB");
        }
        
        String originalFileName = file.getOriginalFilename();
        if (originalFileName == null || originalFileName.trim().isEmpty()) {
            throw new BusinessException("文件名不能为空");
        }
    }
    
    private String generateFileName(String originalFileName) {
        String extension = getFileExtension(originalFileName);
        return UUID.randomUUID().toString() + extension;
    }
    
    private String generateFilePath(String fileName) {
        String datePath = Instant.now().toString().substring(0, 10); // YYYY-MM-DD
        return UPLOAD_DIR + "/" + datePath + "/" + fileName;
    }
    
    private String getFileExtension(String fileName) {
        int lastDotIndex = fileName.lastIndexOf('.');
        return lastDotIndex > 0 ? fileName.substring(lastDotIndex) : "";
    }
    
    private FileType detectFileType(String mimeType, String fileName) {
        if (mimeType != null) {
            if (mimeType.startsWith("image/")) return FileType.IMAGE;
            if (mimeType.startsWith("video/")) return FileType.VIDEO;
            if (mimeType.startsWith("audio/")) return FileType.AUDIO;
            if (mimeType.contains("pdf") || mimeType.contains("document") || mimeType.contains("text")) return FileType.DOCUMENT;
            if (mimeType.contains("zip") || mimeType.contains("rar") || mimeType.contains("7z")) return FileType.ARCHIVE;
        }
        
        String extension = getFileExtension(fileName).toLowerCase();
        if (extension.matches("\\.(jpg|jpeg|png|gif|bmp|svg|webp)")) return FileType.IMAGE;
        if (extension.matches("\\.(mp4|avi|mov|wmv|flv|webm)")) return FileType.VIDEO;
        if (extension.matches("\\.(mp3|wav|flac|aac|ogg)")) return FileType.AUDIO;
        if (extension.matches("\\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt)")) return FileType.DOCUMENT;
        if (extension.matches("\\.(zip|rar|7z|tar|gz)")) return FileType.ARCHIVE;
        if (extension.matches("\\.(java|js|py|cpp|c|html|css|xml|json)")) return FileType.CODE;
        
        return FileType.OTHER;
    }
    
    private String calculateFileHash(byte[] content) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] hash = md.digest(content);
            StringBuilder sb = new StringBuilder();
            for (byte b : hash) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new BusinessException("文件哈希计算失败");
        }
    }
    
    private Specification<File> buildSpecification(FileFilter filter) {
        return (root, query, cb) -> {
            List<jakarta.persistence.criteria.Predicate> predicates = new java.util.ArrayList<>();
            
            if (filter.getKeyword() != null && !filter.getKeyword().trim().isEmpty()) {
                String keyword = "%" + filter.getKeyword().trim() + "%";
                predicates.add(cb.or(
                    cb.like(root.get("fileName"), keyword),
                    cb.like(root.get("originalFileName"), keyword),
                    cb.like(root.get("description"), keyword),
                    cb.like(root.get("tags"), keyword)
                ));
            }
            
            if (filter.getFileType() != null) {
                predicates.add(cb.equal(root.get("fileType"), filter.getFileType()));
            }
            
            if (filter.getStatus() != null) {
                predicates.add(cb.equal(root.get("status"), filter.getStatus()));
            }
            
            if (filter.getUploadedBy() != null) {
                predicates.add(cb.equal(root.get("uploadedBy"), filter.getUploadedBy()));
            }
            
            if (filter.getTicketId() != null) {
                predicates.add(cb.equal(root.get("ticketId"), filter.getTicketId()));
            }
            
            if (filter.getCommentId() != null) {
                predicates.add(cb.equal(root.get("commentId"), filter.getCommentId()));
            }
            
            if (filter.getCreatedFrom() != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), filter.getCreatedFrom()));
            }
            
            if (filter.getCreatedTo() != null) {
                predicates.add(cb.lessThanOrEqualTo(root.get("createdAt"), filter.getCreatedTo()));
            }
            
            if (filter.getMinSize() != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("fileSize"), filter.getMinSize()));
            }
            
            if (filter.getMaxSize() != null) {
                predicates.add(cb.lessThanOrEqualTo(root.get("fileSize"), filter.getMaxSize()));
            }
            
            if (filter.getMimeType() != null && !filter.getMimeType().trim().isEmpty()) {
                predicates.add(cb.like(root.get("mimeType"), "%" + filter.getMimeType().trim() + "%"));
            }
            
            return cb.and(predicates.toArray(new jakarta.persistence.criteria.Predicate[0]));
        };
    }
    
    private FileDto convertToDto(File file) {
        String uploadedByName = userRepository.findById(file.getUploadedBy())
                .map(user -> user.getFullName())
                .orElse("未知用户");
        
        return FileDto.builder()
                .id(file.getId())
                .fileName(file.getFileName())
                .originalFileName(file.getOriginalFileName())
                .filePath(file.getFilePath())
                .fileSize(file.getFileSize())
                .mimeType(file.getMimeType())
                .fileType(file.getFileType())
                .status(file.getStatus())
                .description(file.getDescription())
                .tags(file.getTags())
                .uploadedBy(file.getUploadedBy())
                .uploadedByName(uploadedByName)
                .ticketId(file.getTicketId())
                .commentId(file.getCommentId())
                .fileHash(file.getFileHash())
                .metadata(file.getMetadata())
                .downloadCount(file.getDownloadCount())
                .lastAccessedAt(file.getLastAccessedAt())
                .createdAt(file.getCreatedAt())
                .updatedAt(file.getUpdatedAt())
                .build();
    }
}
